Skip navigation and go to content

Inject a Page Heading with a React Portal

On this page

Every web page needs a single H1 that describes the most important piece of content on the page.

It could introduce the overall site or the most important content on a single page.

The decision is subjective, but you do need to choose something to provide information to Assistive Technology.

💡Tip

The <title> in the HTML document head also contributes to high-level information read aloud in screen readers.

The Listing Detail page has a much better heading structure than when we started, but it’s still missing an H1.

Web Developer Toolbar visualizes the page’s heading structure

As we've seen, the markup for our Header component where the MegaNav is imported is in a completely different place than our Listing page template.

The H1 that we need to add should come before the MegaNav H2 headings. But it should also reflect information from the dynamically created Listing Detail page that is structured below the nav headings.

We could open quite a can of worms to refactor multiple components for the purpose of changing heading levels.

Fortunately, React's Portals feature gives us an escape hatch as one option.

💡Tip

If you aren’t a React user, other frameworks & libraries may offer similar functionality. For example, Vue has a Teleport feature that should let you accomplish this task.

Introducing the HeaderPortal Component

React Portals allow us to render into a DOM node that is outside of our current component’s DOM hierarchy.

In order words, we can set things up so that our Listing Detail page can tell an H1 to render somewhere else.

We’ll do this with the HeaderPortal component, which can be found at components/header-portal.js.

Here’s the complete source code for the component:

// components/header-portal.js
import React, { useEffect } from "react"
import { createPortal } from "react-dom"

const HeaderPortal = ({children}) => {
    const mount = document.getElementById("portal-root")
    const el = document.createElement("div")

    useEffect(() => {
        mount.appendChild(el)
        return () => mount.removeChild(el)
    }, [el, mount])
    
    return createPortal(children, el)
}

With the help from React, the HeaderPortal component searches the DOM for an element with an id of portal-root and appends to it a div containing whatever children were provided.

So if we write JSX like this:

<HeaderPortal>
	<p>Hello World</p>
</HeaderPortal>

Then React will create a new div that contains a paragraph tag with “Hello World” in it, then append that div to the portal-root.

💡Tip

Learn more about useEffect and Portals in the React docs.

The portal-root Element

Inside of index.html in the root directory of the project, there are a couple of divs in the body tag:

// inside of index.html
<body>
	<div id="portal-root"></div>
	<div id="app-root"></div>
	<script type="module" src="index.js"></script>
</body>

The app-root div is where React renders our entire application, as seen in index.js.

Above that is the portal-root div, which is where the HeaderPortal component has been told to render.

So anything that the HeaderPortal renders will be above the hierarchy of everything else in the application.

Video: Introducing the HeaderPortal Component
Loaded: 4%
Current Time 0:00
/
Duration Time 2:11
Video Transcript

We still have this issue of the missing h1. So let's go deal with that in our react application, we have a component that we can pull in and I'm gonna open up our sidebar.

Show you this component. So the challenge we've got here is that listing, we are kind of being popped all the way down into the page content. You know, we, we're not looking at the mega menu here. We, you know, that's a separate part of our application. If we were going to go and do like our complete redo of the heading structure, we would be going through a lot more components.

Whereas what we want here is when I'm on this listing detail, I want to try and inject a heading way up in the page. So kind of need a little escape hatch to be able to do that. Fortunately, I created one in this project it's called the header portal. So this is a very react specific way to do this.

You know, if you had some other kind of templating system, Maybe you have some other mechanism to go get a heading put up there. Maybe it's some kind of metadata that the template uses. Like there's all different ways that in a complex web application that we might need to work within some constraints.

So for this it takes our, this is a component I'm showing it to you, but you don't, you won't really need to work with it directly except to we'll call it like a regular component

I'm going to go over here to index.html. It's going to inject content into this portal root div back in our index.html file.

So all of our application content is in the app route, but our escape hatch gives us this little space that we can go put content if it needs to be all the way at the top of the document so that we can go inject an h1 to be the first thing on the page. It wasn't my first choice. You know, I didn't love this solution.

But at some, sometimes you gotta do what works and this was a way to make it work. So I'll show you how we do that in this context. You know, if I had a fully static HTML page, I could go through and craft the heading structure, however I want. But I may or may not have access to create stuff in that part of the template.

🛠️ Challenge: Add an H1 with React Portal

Your challenge is to import the HeaderPortal component and use it to add an h1 to the top of the Listing Detail page structure.

You can look at the solution inside of the exercise1-headings-landmarks directory if you need help, or just move on to the solution.

🛠️ Solution: Add an H1 with React Portal

Inside of page-listing-detail.js, import the HeaderPortal component.

I’m going to add our portal component above the main content that gets rendered:

// inside page-listing-detail.js
return (
  <BodyClassName className="header-overlap page-listing-detail">
    <>
      <HeaderPortal>
      </HeaderPortal>
      <div>
          <div
              className="page-header"
              style={{backgroundImage: `url(${headerImageUrl}`}}
          >
      ...

Following the pattern above, we can add an H1 tag as a child to the HeaderPortal component.

We could write “Camp Spots” as the highest-level piece of information on the page, but adding the listing name would make it more relevant to this particular page:

<HeaderPortal>
  <h1>Camp Spots - {data.listingName}</h1>
</HeaderPortal>

Saving our work, here’s what it looks like in the browser:

The “Camp Spots - Cranberry Lake” heading is rendered at the top of the pageLoading

The heading is rendered at the very top of the entire application, just like we saw would happen in index.html.

Running Web Developer Toolbar again, we no longer have the missing H1.

Camp Spots - Cranberry Lake shows up as the h1 in the Document OutlineLoading

Now the page structure is great, but it doesn’t really look great.

Let’s work around that.

Visually Hiding the Heading

Adding a visually-hidden class to global-styles/styles.scss will allow the heading to be rendered to the DOM but not visually shown.

Here’s the CSS in the global stylesheet:

.visually-hidden {
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}
💡Tip

We go more in-depth on visual hiding strategies in the Coding Accessible Interactions & Mechanics workshop!

Now we can add the visually-hidden class to the H1 in our portal using React’s className prop:

<HeaderPortal>
	<h1 className="visually-hidden">Camp Spots - {data.listingName}</h1>
</HeaderPortal>

Once we save our work, the visual layout goes back to how it was, but we still have a nice heading structure in place.

The H1 is no longer visually rendered but is still in the document outline
Video: Solution: Add an H1 with React Portal
Loaded: 2%
Current Time 0:00
/
Duration Time 3:26
Video Transcript

So for this solution, let's do the heading portal. So in page listing detail, we need to import header portal from components, slash header portal. So that matches our component that we've got for our little escape hatch are very react way of doing things. So within our component, I can instantiate header portal, like any other components.

So it's a named component. And this is literally like a little escape hatch to go in and inject content into that div that we saw. So within here, I could say, I could say camp spots if I want my h1. If I decided maybe I wanted to structure this page slightly differently, like our listing name, if I was going to create, I don't know, some alternative structure, like maybe camp spots.

Data.listingName. You know, like if I wanted something that created a unique h1 for this page that kind of works in the page content, you know, I can get creative here within this template. So let's, let's put that in there for now. So camp spots We've got something just to show us like how we can inject an h1 kind of like the, the subjectivity of what exactly the h1 is going to be that can vary so much.

I just want to show you how, how we're going to go and add something so that we get that full page structure starting with an h1 so I'm going to come back to the browser. Here's our camp spots, cranberry lake. So it's literally injecting it there. Like we see it and everything. And if I run our web developer toolbar again, Cool.

Now we don't have a missing h1 anymore. And you know, we could play around with what exactly we want our h1 text to say. You know, if we were really like hamming up the marketing, it could be like, book your stay now at cranberry lake. I'm sure you're working with the marketing team. They might have opinions about what's going to convert the best for each one.

What's going to get the best search engine optimization. We're just going to leave that as an exercise to consider, but the thing that we do need, cause we don't, I don't want this to be a visually rendered heading. This is there to create the whole page structure. This is where our class name trick with visually hidden comes in.

We can use that here. So that coming back to our browser. Now our layout kind of, you know, goes seamlessly back to the way it was, but we still have a nice heading structure. So camp spots, cranberry lake for h1, it's still rendered, you know, capital R rendered, but it's visually hidden so that our layout looks the way that it did before.

So that's a technique, you know, it's a solution to create this more holistic structure. And, you know, you might make slightly different choices or different decisions, depending on what constraints you're working with, what your heading structure needs to be. But we always want something that kind of overall gives us a heading structure that reflects what content is on the page.

Wrapping Up Headings

A good heading structure ensures that screen reader users will be able to navigate our pages efficiently.

There are subjective decisions about what should be headings, so talk to other team members for input on what makes sense from SEO and marketing perspectives as well as from information architecture perspectives.

The structure of your application may have some implications for how you can construct your headings, but your goal should be to create an overall heading structure that sets up your content for screen reader access. With help from tools like React Portals and CSS hiding techniques, you can make a solid heading structure happen no matter what.